/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/graphics.hpp>

#include <baja/graphics/scenes/scenes_fwd.hpp>
#include <baja/graphics/scenes/scenes_interfaces.hpp>
#include <baja/graphics/scenes/details/scene_node_base.hpp>

namespace baja { namespace graphics { namespace scenes {

namespace d = ::baja::graphics::scenes::details;

class mesh_node : public d::scene_node_base<imesh_node>
{
public:
    mesh_node(
        const std::shared_ptr<imesh>& mesh, 
        const std::shared_ptr<imaterial_collection>& materials
        )
        : _mesh(mesh), _materials(materials)
    {
    }

    static std::shared_ptr<mesh_node> create(
        const std::shared_ptr<imesh>& mesh, 
        const std::shared_ptr<imaterial_collection>& materials
        )
    {
        std::shared_ptr<mesh_node> returnValue = std::make_shared<mesh_node>(mesh, materials);
        return returnValue;
    }

    // gets the mesh
    std::shared_ptr<imesh> getMesh()
    {
        return _mesh;
    }

    // sets the mesh
    void setMesh(const std::shared_ptr<imesh>& mesh)
    {
        _mesh = mesh;
        
        std::shared_ptr<imaterial_collection> materialsClone = mesh->cloneMaterialCollection();
        _materials = materialsClone;
        this->setNodeDirty();
    }

    // gets the material collection for this mesh node
    std::shared_ptr<imaterial_collection> getMaterialCollection()
    {
        return _materials;
    }

    // render the mesh
    void renderInternal( 
        const std::shared_ptr<iengine>& graphics,
        const m::matrix44* localToWorld
        )
    {
        bool pushedMatrix = false;

        if (localToWorld == nullptr)
        {
            // we are using matrix stack to properly transform matrices
            m::matrix44 mat;
            this->getLocalToParent(&mat);
            graphics->pushWorldMatrix(mat, true);
            pushedMatrix = true;
        }

        if (_mesh.get() != nullptr)
        {
            if (this->getEffect() == nullptr)
            {
                _mesh->render(
                    graphics,
                    _materials,
                    localToWorld,
                    this->getDefaultEffect()
                    );
            }
            else
            {
                _mesh->renderWithEffect(
                    graphics, 
                    _materials,
                    localToWorld,
                    this->getEffect()
                    );
            }
        }

        if (pushedMatrix)
        {
            (void)graphics->popWorldMatrix();
        }
    }

    // performs hit testing from pixel space to the mesh node
    bool pixelHitTest(
        const std::shared_ptr<iscene_transformer>& sceneTransformer,
        const m::point2d& pixel,
        m::point3d* hitPositionLocalSpace)
    {
        // assume we won't intersect
        bool hitTestPassed = false;

        if (this->getIsVisible())
        {
            if (_mesh.get() != nullptr)
            {
                // determine the line segment in local mesh space
                m::line3d worldLine;
                m::line3d localLine;
                float32 t;

                worldLine = sceneTransformer->pixelToWorldSpace(pixel);
                this->getWorldToLocal().apply(worldLine, &localLine);

                // check mesh for intersection
                hitTestPassed = _mesh->lineHitTest(localLine, &t);
                if (hitTestPassed)
                {
                    if (hitPositionLocalSpace != nullptr)
                    {
                        m::point3d intersectionPoint;
                        localLine.getPointAt(t, &intersectionPoint);
                        *hitPositionLocalSpace = intersectionPoint;
                    }
                }
            }
        }

        return hitTestPassed;
    }

private:
    std::shared_ptr<imesh> _mesh;
    std::shared_ptr<imaterial_collection> _materials;
};

inline std::shared_ptr<imesh_node> createMeshNode(const std::shared_ptr<imesh>& mesh)
{
    std::shared_ptr<imaterial_collection> materialsClone = mesh->cloneMaterialCollection();

    std::shared_ptr<mesh_node> returnValue = mesh_node::create(mesh, materialsClone);
    return std::dynamic_pointer_cast<imesh_node>(returnValue);
}

}}} // namespace baja::graphics::scenes
